//motorola 6821, based off of mame's 6821 code

#include "StdAfx.h"
#include "6821.h"

C6821::C6821()
{
Init();
user = 0;
}

C6821::~C6821()
{
}

void C6821::Init()
{
InputAFunc = 0;
InputBFunc = 0;
InputCA1Func = 0;
InputCA2Func = 0;
InputCB1Func = 0;
InputCB2Func = 0;
OutputAFunc = 0;
OutputBFunc = 0;
OutputCA2Func = 0;
OutputCB2Func = 0;
IRQAFunc = 0;
IRQBFunc = 0;
}

void C6821::Reset()
{
DataA = DataB = 0;
ControlA = ControlB = 0;
InputA = OutputA = 0;
InputB = OutputB = 0;
InputCA1 = InputCA2 = OutputCA2 = 0;
InputCB1 = InputCB2 = OutputCB2 = 0;
IRQA1 = IRQA2 = 0;
IRQB1 = IRQB2 = 0;
IRQAState = IRQBState = 0;
InputA = InputCA1 = InputCA2 = 0xFF;
}

void C6821::update_shared_irq_handler(void (*irq_func)(void*))
{
if(IRQAFunc == irq_func && IRQAState)
	{
	(*irq_func)(user);
	return;
	}
if(IRQBFunc == irq_func && IRQBState)
	{
	(*irq_func)(user);
	return;
	}
}

void C6821::update_6821_interrupts()
{
	int new_state;

	/* start with IRQ A */
	new_state = 0;
	if ((IRQA1 && ControlA & CTRL_IRQ1_ENABLED) || (IRQA2 && ControlA & CTRL_IRQ2_ENABLED)) new_state = 1;
	if (new_state != IRQAState)
	{
		IRQAState = new_state;
		if (IRQAFunc) update_shared_irq_handler(IRQAFunc);
	}

	/* then do IRQ B */
	new_state = 0;
	if ((IRQB1 && ControlB & CTRL_IRQ1_ENABLED) || (IRQB2 && ControlB & CTRL_IRQ2_ENABLED)) new_state = 1;
	if (new_state != IRQBState)
	{
		IRQBState = new_state;
		if (IRQBFunc) update_shared_irq_handler(IRQBFunc);
	}
}

void C6821::set_input_ca1(int data)
{
	/* limit the data to 0 or 1 */
	data = data ? 1 : 0;

	/* the new state has caused a transition */
	if(InputCA1 ^ data)
	{

		/* handle the active transition */
		if ((data && (ControlA & 2)) || (!data && (ControlA & 2) == 0))
		{
			/* mark the IRQ */
			IRQA1 = 1;

			/* update externals */
			update_6821_interrupts();

			/* CA2 is configured as output and in read strobe mode and cleared by a CA1 transition */
			if(ControlA & CTRL_C2_OUTPUT && 
				(ControlA & CTRL_C2_SETMODE) == 0 && 
				(ControlA & CTRL_STROBE_E_RESET) == 0)
			{
				/* call the CA2 output function */
				if (OutputCA2 == 0 && OutputCA2Func) OutputCA2Func(user,1);

				/* clear CA2 */
				OutputCA2 = 1;
			}
		}
	}

	/* set the new value for CA1 */
	InputCA1 = data;
//	p->in_set |= PIA_IN_SET_CA1;
}

void C6821::set_input_ca2(int data)
{
	/* limit the data to 0 or 1 */
	data = data ? 1 : 0;

	/* CA2 is in input mode */
	if((ControlA & CTRL_C2_OUTPUT) == 0)
	{
		/* the new state has caused a transition */
		if (InputCA2 ^ data)
		{
			/* handle the active transition */
			if ((data && (ControlA & 2)) || (!data && (ControlA & 2) == 0))
			{
				/* mark the IRQ */
				IRQA2 = 1;

				/* update externals */
				update_6821_interrupts();
			}
		}
	}

	/* set the new value for CA2 */
	InputCA2 = data;
//	p->in_set |= PIA_IN_SET_CA2;
}

void C6821::set_input_cb1(int data)
{
	/* limit the data to 0 or 1 */
	data = data ? 1 : 0;

	/* the new state has caused a transition */
	if (InputCB1 ^ data)
	{
		/* handle the active transition */
		if ((data && (ControlB & 2)) || (!data && (ControlB & 2) == 0))
		{
			/* mark the IRQ */
			IRQB1 = 1;

			/* update externals */
			update_6821_interrupts();

			/* If CB2 is configured as a write-strobe output which is reset by a CB1
               transition, this reset will only happen when a read from port B implicitly
               clears the IRQ B1 flag.  So we handle the CB2 reset there.  Note that this
               is different from what happens with port A. */
		}
	}

	/* set the new value for CB1 */
	InputCB1 = data;
//	p->in_set |= PIA_IN_SET_CB1;
}

void C6821::set_input_cb2(int data)
{
	/* limit the data to 0 or 1 */
	data = data ? 1 : 0;

	/* CB2 is in input mode */
	if((ControlB & CTRL_C2_OUTPUT) == 0)
	{
		/* the new state has caused a transition */
		if (InputCB2 ^ data)
		{
			/* handle the active transition */
			if ((data && (ControlB & 2)) || (!data && (ControlB & 2) == 0))
			{
				/* mark the IRQ */
				IRQB2 = 1;

				/* update externals */
				update_6821_interrupts();
			}
		}
	}

	/* set the new value for CA2 */
	InputCB2 = data;
//	p->in_set |= PIA_IN_SET_CB2;
}

u8 C6821::Read(int reg)
{
u8 ret;

switch(reg)
	{
	case c6821_ddra:
		if(ControlA & CTRL_OUTPUT)				//output selected
			{
			if(InputAFunc)							//function specified to get input data?
				InputA = InputAFunc(user);
			ret = OutputA & DataA;				//combine input and output data
			ret |= InputA & ~DataA;
			IRQA1 = 0;								//clear irq data
			IRQA2 = 0;
			update_6821_interrupts();
			if(ControlA & CTRL_C2_OUTPUT &&	//ca2 is output and strobe is on
				(ControlA & CTRL_C2_SETMODE) == 0)
				{
				if(OutputCA2 && OutputCA2Func)//if output ca2 specified
					OutputCA2Func(user,0);		//send low signal
				OutputCA2 = 0;
				if(ControlA & CTRL_STROBE_E_RESET)
					{
					if(OutputCA2Func)				//if output ca2 specified
						OutputCA2Func(user,1);	//send high signal
					OutputCA2 = 1;
					}
				}
			}
		else											//input selected, read data register
			ret = DataA;
		return(ret);
	case c6821_ctrla:
		if(InputCA1Func)
			set_input_ca1(InputCA1Func(user));
		if(InputCA2Func)
			set_input_ca2(InputCA2Func(user));
		ret = ControlA;
		if(IRQA1)
			ret |= 0x80;
		if(IRQA2 && (ControlA & CTRL_C2_OUTPUT) == 0)
			ret |= 0x40;
		return(ret);
	case c6821_ddrb:
		if(ControlB & CTRL_OUTPUT)				//output selected
			{
			if(InputBFunc)							//function specified to get input data?
				InputB = InputBFunc(user);
			ret = OutputB & DataB;				//combine input and output data
			ret |= InputB & ~DataB;
			if(IRQB1 && 
				ControlB & CTRL_C2_OUTPUT && 
				(ControlB & CTRL_C2_SETMODE) == 0 &&
				(ControlB & CTRL_STROBE_E_RESET) == 0)
				{
				if(OutputCB2 == 0 && OutputCB2Func)
					OutputCB2Func(user,1);		//send high signal
				OutputCB2 = 1;
				}
			IRQB1 = 0;								//clear irq data
			IRQB2 = 0;
			update_6821_interrupts();
			}
		else											//input selected, read data register
			ret = DataB;
		return(ret);
	case c6821_ctrlb:
		if(InputCB1Func)
			set_input_cb1(InputCB1Func(user));
		if(InputCB2Func)
			set_input_cb2(InputCB2Func(user));
		ret = ControlB;
		if(IRQB1)
			ret |= 0x80;
		if(IRQB2 && (ControlB & CTRL_C2_OUTPUT) == 0)
			ret |= 0x40;
		return(ret);
	}
return(0);
}

void C6821::Write(int reg,u8 data)
{
switch(reg)
	{
	case c6821_ddra:
		if(ControlA & CTRL_OUTPUT)
			{
			OutputA = data;
			if(OutputAFunc && DataA)
				OutputAFunc(user,OutputA & DataA);
			}
		else
			{
			if(DataA != data)
				{
				DataA = data;
				if(OutputAFunc && DataA)
					OutputAFunc(user,OutputA & DataA);
				}
			}
		break;
	case c6821_ctrla:
		data &= 0x3F;
		if(data & CTRL_C2_OUTPUT)
			{
			u8 t;

			if(data & CTRL_C2_SETMODE)
				{
				if((data & 8) == 0)
					t = 0;
				else
					t = 1;
				}
			else
				t = 1;
			if((ControlA & CTRL_C2_OUTPUT) == 0 ||
				((ControlA & CTRL_C2_OUTPUT) && (OutputCA2 ^ t)))
				{
				if(OutputCA2Func)
					OutputCA2Func(user,t);
				}
			OutputCA2 = t;
			}
		ControlA = data;
		update_6821_interrupts();
		break;
	case c6821_ddrb:
		if(ControlB & CTRL_OUTPUT)
			{
			OutputB = data;
			if(OutputBFunc && DataB)
				OutputBFunc(user,OutputB & DataB);
			if(ControlB & CTRL_C2_OUTPUT && (ControlB & CTRL_C2_SETMODE) == 0)
				{
				if(OutputCB2 && OutputCB2Func)
					OutputCB2Func(user,0);
				OutputCB2 = 0;
				if(ControlB & CTRL_STROBE_E_RESET)
					{
					if(OutputCB2Func)
						OutputCB2Func(user,1);
					OutputCB2 = 1;
					}
				}
			}
		else
			{
			if(DataB != data)
				{
				DataB = data;
				if(OutputBFunc && DataB)
					OutputBFunc(user,OutputB & DataB);
				}
			}
		break;
	case c6821_ctrlb:
		data &= 0x3F;
		if(data & CTRL_C2_OUTPUT)
			{
			u8 t;

			if(data & CTRL_C2_SETMODE)
				{
				if((data & 8) == 0)
					t = 0;
				else
					t = 1;
				}
			else
				t = 1;
			if((ControlB & CTRL_C2_OUTPUT) == 0 ||
				((ControlB & CTRL_C2_OUTPUT) && (OutputCB2 ^ t)))
				{
				if(OutputCB2Func)
					OutputCB2Func(user,t);
				}
			OutputCB2 = t;
			}
		ControlB = data;
		update_6821_interrupts();
		break;
	}
}

void C6821::SetInputA(u8 data)
{
InputA = data;
}

void C6821::SetInputB(u8 data)
{
InputB = data;
}
